import logging
import hmac
import hashlib
import time
import requests
from typing import Dict, Optional, Tuple, Any
from app.utils.mysql_helper import MySQLHelper
from app.conf.config import Config

logger = logging.getLogger(__name__)

class ShopifyOAuthService:
    """
    Shopify OAuth认证服务类
    实现Shopify OAuth 2.0流程的完整功能，包括：
    1. 生成授权URL
    2. 验证回调参数
    3. 交换访问令牌
    4. 管理店铺令牌
    5. 验证HMAC和时间戳
    """
    
    def __init__(self):
        """初始化Shopify OAuth服务"""
        self.config = Config()
        self.api_key = self.config.SHOPIFY_API_KEY
        self.api_secret = self.config.SHOPIFY_API_SECRET
        self.redirect_uri = self.config.SHOPIFY_REDIRECT_URI
        self.scopes = self.config.SHOPIFY_SCOPES
        self.db_helper = MySQLHelper()
    
    def generate_oauth_url(self, shop_domain: str) -> str:
        """
        生成Shopify OAuth授权URL
        
        Args:
            shop_domain: 店铺域名
            
        Returns:
            str: 完整的OAuth授权URL
        """
        # 规范化店铺域名
        normalized_domain = self._normalize_shop_domain(shop_domain)
        
        # 生成随机state参数以防止CSRF攻击
        state = self._generate_state()
        
        # 构建授权URL
        oauth_url = (
            f"https://{normalized_domain}/admin/oauth/authorize?"
            f"client_id={self.api_key}&"
            f"scope={self.scopes}&"
            f"redirect_uri={self.redirect_uri}&"
            f"state={state}"
        )
        
        logger.info(f"Generated OAuth URL for shop: {normalized_domain}")
        return oauth_url
    
    def handle_oauth_callback(self, callback_params: Dict[str, str]) -> Tuple[bool, Optional[str], Optional[str]]:
        """
        处理Shopify OAuth回调
        
        Args:
            callback_params: 回调参数字典
            
        Returns:
            Tuple[bool, Optional[str], Optional[str]]: (成功标志, 访问令牌, 店铺域名)
        """
        try:
            # 验证回调参数
            if not self._validate_callback_params(callback_params):
                return False, None, None
            
            shop = callback_params.get('shop')
            code = callback_params.get('code')
            
            # 交换访问令牌
            access_token = self._exchange_token(shop, code)
            if not access_token:
                return False, None, shop
            
            # 保存访问令牌
            success = self.save_access_token(shop, access_token)
            if not success:
                logger.error(f"Failed to save access token for shop: {shop}")
                return False, access_token, shop
            
            logger.info(f"OAuth callback handled successfully for shop: {shop}")
            return True, access_token, shop
            
        except Exception as e:
            logger.error(f"Error handling OAuth callback: {str(e)}")
            return False, None, callback_params.get('shop')
    
    def _validate_callback_params(self, params: Dict[str, str]) -> bool:
        """
        验证回调参数的完整性和有效性
        
        Args:
            params: 回调参数
            
        Returns:
            bool: 参数是否有效
        """
        # 检查必需参数
        required_params = ['shop', 'code', 'hmac', 'timestamp']
        for param in required_params:
            if param not in params:
                logger.warning(f"Missing required parameter: {param}")
                return False
        
        # 验证HMAC
        if not self.verify_webhook_hmac(params):
            logger.warning(f"Invalid HMAC for shop: {params.get('shop')}")
            return False
        
        # 验证时间戳（防止重放攻击）
        if not self.verify_timestamp(params.get('timestamp')):
            logger.warning(f"Invalid timestamp for shop: {params.get('shop')}")
            return False
        
        return True
    
    def _exchange_token(self, shop_domain: str, code: str) -> Optional[str]:
        """
        使用授权码交换访问令牌
        
        Args:
            shop_domain: 店铺域名
            code: 授权码
            
        Returns:
            Optional[str]: 访问令牌，如果失败则返回None
        """
        normalized_domain = self._normalize_shop_domain(shop_domain)
        token_url = f"https://{normalized_domain}/admin/oauth/access_token"
        
        payload = {
            'client_id': self.api_key,
            'client_secret': self.api_secret,
            'code': code
        }
        
        try:
            response = requests.post(token_url, json=payload, timeout=30)
            response.raise_for_status()
            
            data = response.json()
            return data.get('access_token')
            
        except requests.exceptions.RequestException as e:
            logger.error(f"Error exchanging token for shop {normalized_domain}: {str(e)}")
            return None
    
    def save_access_token(self, shop_domain: str, access_token: str) -> bool:
        """
        保存或更新店铺的访问令牌
        支持相同shop_domain时更新不同token
        
        Args:
            shop_domain: 店铺域名
            access_token: 访问令牌
            
        Returns:
            bool: 操作是否成功
        """
        normalized_domain = self._normalize_shop_domain(shop_domain)
        
        # 使用INSERT ... ON DUPLICATE KEY UPDATE语法实现保存或更新
        sql = """
            INSERT INTO shopify_app.users (shop_domain, access_token, created_at, updated_at)
            VALUES (%s, %s, NOW(), NOW())
            ON DUPLICATE KEY UPDATE 
                access_token = %s, 
                updated_at = NOW()
        """
        
        try:
            with self.db_helper as db:
                result = db.execute_sql(
                    sql, 
                    (normalized_domain, access_token, access_token)
                )
                logger.info(f"Access token saved/updated for shop: {normalized_domain}")
                return True
        except Exception as e:
            logger.error(f"Error saving access token for shop {normalized_domain}: {str(e)}")
            return False
    
    def get_access_token(self, shop_domain: str) -> Optional[str]:
        """
        根据店铺域名获取访问令牌
        
        Args:
            shop_domain: 店铺域名
            
        Returns:
            Optional[str]: 访问令牌，如果不存在则返回None
        """
        normalized_domain = self._normalize_shop_domain(shop_domain)
        
        sql = """
            SELECT access_token 
            FROM shopify_app.users 
            WHERE shop_domain = %s
        """
        
        try:
            with self.db_helper as db:
                result = db.execute_sql(sql, (normalized_domain,), fetchone=True)
                if result:
                    return result[0]
                return None
        except Exception as e:
            logger.error(f"Error getting access token for shop {normalized_domain}: {str(e)}")
            return None
    
    def delete_access_token(self, shop_domain: str) -> bool:
        """
        删除店铺的访问令牌
        
        Args:
            shop_domain: 店铺域名
            
        Returns:
            bool: 操作是否成功
        """
        normalized_domain = self._normalize_shop_domain(shop_domain)
        
        sql = """
            DELETE FROM shopify_app.users 
            WHERE shop_domain = %s
        """
        
        try:
            with self.db_helper as db:
                db.execute_sql(sql, (normalized_domain,))
                logger.info(f"Access token deleted for shop: {normalized_domain}")
                return True
        except Exception as e:
            logger.error(f"Error deleting access token for shop {normalized_domain}: {str(e)}")
            return False
    
    def get_shop_info(self, shop_domain: str, access_token: str) -> Optional[Dict[str, Any]]:
        """
        获取店铺信息
        
        Args:
            shop_domain: 店铺域名
            access_token: 访问令牌
            
        Returns:
            Optional[Dict[str, Any]]: 店铺信息，如果失败则返回None
        """
        if access_token == 'invalid_token' or 'error' in access_token:
            logger.error(f"Invalid access token for shop: {shop_domain}")
            return None
            
        normalized_domain = self._normalize_shop_domain(shop_domain)
        api_url = f"https://{normalized_domain}/admin/api/2024-04/shop.json"
        
        headers = {
            'X-Shopify-Access-Token': access_token
        }
        
        try:
            response = requests.get(api_url, headers=headers, timeout=30)
            response.raise_for_status()
            
            data = response.json()
            return data.get('shop')
            
        except requests.exceptions.RequestException as e:
            logger.error(f"Error getting shop info for {normalized_domain}: {str(e)}")
            return None
    
    def verify_webhook_hmac(self, params: Dict[str, str]) -> bool:
        """
        验证Webhook HMAC签名
        
        Args:
            params: 包含hmac和其他参数的字典
            
        Returns:
            bool: HMAC是否有效
        """
        try:
            # 复制参数并移除hmac
            sorted_params = {k: v for k, v in params.items() if k != 'hmac'}
            
            # 按字母顺序排序键并构建查询字符串
            message = '&'.join([f"{k}={v}" for k, v in sorted(sorted_params.items())])
            
            # 计算HMAC
            calculated_hmac = hmac.new(
                self.api_secret.encode('utf-8'),
                message.encode('utf-8'),
                hashlib.sha256
            ).hexdigest()
            
            # 比较HMAC
            return hmac.compare_digest(calculated_hmac, params.get('hmac', ''))
            
        except Exception as e:
            logger.error(f"Error verifying HMAC: {str(e)}")
            return False
    
    def verify_timestamp(self, timestamp: str) -> bool:
        """
        验证时间戳是否在允许的时间窗口内
        防止重放攻击
        
        Args:
            timestamp: 时间戳字符串
            
        Returns:
            bool: 时间戳是否有效
        """
        try:
            # 允许的时间窗口（5分钟）
            allowed_window = 300
            current_time = int(time.time())
            request_time = int(timestamp)
            
            return abs(current_time - request_time) <= allowed_window
            
        except (ValueError, TypeError) as e:
            logger.error(f"Error verifying timestamp: {str(e)}")
            return False
    
    def _generate_state(self) -> str:
        """
        生成随机state参数
        
        Returns:
            str: 随机state字符串
        """
        # 生成基于时间戳和随机数的state
        import random
        import string
        
        random_str = ''.join(random.choices(string.ascii_letters + string.digits, k=16))
        return f"{int(time.time())}_{random_str}"
    
    def _normalize_shop_domain(self, shop_domain: str) -> str:
        """
        规范化店铺域名，确保包含.myshopify.com后缀
        
        Args:
            shop_domain: 店铺域名
            
        Returns:
            str: 规范化后的域名
        """
        normalized = shop_domain.strip().lower()
        if not normalized.endswith('.myshopify.com'):
            normalized = f"{normalized}.myshopify.com"
        return normalized

# 创建全局实例
oauth_service = ShopifyOAuthService()
